home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 1998 March / Macworld (1998-03) (Disk 1).dmg / Shareware World / Utilities / Text Processing / Alpha / Tcl / Modes / bibtexMode.tcl < prev    next >
Encoding:
Text File  |  1997-12-18  |  49.3 KB  |  1,719 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #  Vince's Additions - an extension package for Alpha
  4.  # 
  5.  #  FILE: "bibtexMode.tcl"
  6.  #                                    created: 17/8/94 {9:12:06 am} 
  7.  #                                last update: 18/12/97 {5:33:11 pm} 
  8.  #  Updated by: Vince Darley
  9.  #  E-mail: <darley@fas.harvard.edu>
  10.  #    mail: Division of Engineering and Applied Sciences, Harvard University
  11.  #          Oxford Street, Cambridge MA 02138, USA
  12.  #     www: <http://www.fas.harvard.edu/~darley/>
  13.  #  
  14.  # Major rewrite of most of BibTeX mode.  Original by Tom Pollard.
  15.  # See the end of the BibTeX Help file for a history.
  16.  # 
  17.  # ###################################################################
  18.  ##
  19.  
  20. alpha::mode Bib 1.0.1 bibtexMenu {*.bib *.inspec} { texMenu bibtexMenu } {
  21.     addMenu bibtexMenu "•282"
  22. } uninstall {this-file} help {file "BibTeX Help"}
  23. # to make sure tex-mode is loaded
  24. texMenu
  25.  
  26. newPref v bibAutoIndex 1 Bib "" [list "Never make index" \
  27.     "Ask user when it is necessary" "Always remake when necessary"] index
  28.  
  29. newPref v suffixString    { \\\\} Bib
  30. newPref v prefixString {% } Bib
  31. newPref v fillColumn {65} Bib
  32. newPref f wordWrap {0} Bib
  33. newPref f autoMark {1} Bib
  34.  
  35. ###########################################################################
  36. # Search patterns for entries and cite-keys
  37. #
  38. #     set bibTopPat {^[     ]*@[a-zA-Z]+[\{\(]([-A-Za-z0-9_:/\.]+)}
  39. # match entry type
  40. set bibTopPat {^[     ]*@([a-zA-Z]+)[\{\(]}
  41. # match cite-key
  42. set bibTopPat1 {^[     ]*@[a-zA-Z]+[\{\(][     ]*([^=,     ]+)}    
  43. # match type and cite-key
  44. set bibTopPat2 {^[     ]*@([a-zA-Z]+)[\{\(][     ]*([^=,     ]+)}    
  45. # match first field (no cite-key)
  46. set bibTopPat3 {^[     ]*@([a-zA-Z]+)[\{\(]([     ]*[a-zA-Z]+[     ]*=[     ]*)}    
  47.  
  48. newPref v wordBreak {[a-zA-Z0-9]+} Bib
  49. newPref v wordBreakPreface {[^a-zA-Z0-9]} Bib
  50. newPref v funcExpr $bibTopPat Bib
  51.  
  52. newPref f overwriteBuffer {1} Bib
  53. newPref f fieldBraces {1} Bib
  54. newPref f entryBraces {1} Bib
  55. newPref f segregateStrings {1} Bib
  56. newPref f markStrings {0} Bib
  57. newPref f alignEquals {0} Bib
  58. newPref f zapEmptyFields {0} Bib
  59. newPref f descendingYears {1} Bib
  60. newPref v indentString {   } Bib
  61. newPref v stdAbbrevs {jan feb mar apr may jun jul aug sep oct nov dec} Bib
  62.  
  63. # ◊◊◊◊ Option-click title bar ◊◊◊◊ #
  64. # use TeX routines for Bib mode
  65. proc Bib::OptionTitlebar {} {TeX::OptionTitlebar}
  66. proc Bib::OptionTitlebarSelect {item} {TeX::OptionTitlebarSelect $item}
  67.  
  68. ###########################################################################
  69. # BibTeX Key Bindings.
  70. ###########################################################################
  71. # abbreviations:  <o> = option, <z> = control, <s> = shift, <c> = command
  72. #
  73. bind 'b' <sz>    selectEntry "Bib"
  74. bind 'n' <sz>    nextEntry "Bib"
  75. bind 'p' <sz>    prevEntry "Bib"
  76.  
  77. bind 'f' <sz>    searchFields "Bib"
  78. bind 'm' <sz>    searchEntries "Bib"
  79. bind 'l' <sz>    formatEntry "Bib"
  80.  
  81. ###########################################################################
  82. # Data Definitions
  83. ###########################################################################
  84. ###########################################################################
  85. # Define the data arrays that contain the names of the required,
  86. # optional, and preferred fields for each entry type.
  87. #
  88. # The index names of the rqdFld() array _define_ the valid entry types
  89. # recognized by the program.
  90. #
  91. set rqdFld(article) {author title journal year} 
  92. set optFld(article) {volume number pages month note}
  93. set myFld(article) {author title journal volume pages year note} 
  94.  
  95. set rqdFld(book) {author title publisher year} 
  96. set optFld(book) {editor volume number series address edition month note}
  97.  
  98. set rqdFld(booklet) {title} 
  99. set optFld(booklet) {author howpublished address month year note}
  100.  
  101. set rqdFld(conference) {author title booktitle year} 
  102. set optFld(conference) {editor volume number series pages organization publisher address month note}
  103.  
  104. set rqdFld(inBook) {author title chapter publisher year} 
  105. set optFld(inBook) {editor pages volume number series address edition month type note}
  106.  
  107. set rqdFld(inCollection) {author title booktitle publisher year} 
  108. set optFld(inCollection) {editor volume number series type chapter pages address edition month note}
  109.  
  110. set rqdFld(inProceedings) {author title booktitle year} 
  111. set optFld(inProceedings) {editor volume number series pages organization publisher address month note}
  112.  
  113. set rqdFld(manual) {title} 
  114. set optFld(manual) {author organization address edition year month note}
  115.  
  116. set rqdFld(mastersThesis) {author title school year} 
  117. set optFld(mastersThesis) {address month note type}
  118.  
  119. set rqdFld(misc) {} 
  120. set optFld(misc) {author title howpublished year month note}
  121.  
  122. set rqdFld(phdThesis) {author title school year} 
  123. set optFld(phdThesis) {address month type note}
  124.  
  125. set rqdFld(proceedings) {title year} 
  126. set optFld(proceedings) {editor volume number series publisher organization address month note}
  127.  
  128. set rqdFld(techReport) {author title institution year} 
  129. set optFld(techReport) {type number address month note}
  130.  
  131. set rqdFld(unpublished) {author title note} 
  132. set optFld(unpublished) {year month}
  133.  
  134. set entryNames [lsort [array names rqdFld]]
  135. set customEntries [lsort [array names myFld]]
  136.  
  137. ###########################################################################
  138. # Define an array of flags indicating whether the data a given field
  139. # type should be quoted.  The actual characters used to quote the field are
  140. # given by $bibOpenQuote and $bibCloseQuote, which are set by the routine
  141. # 'bibFieldDelims' according to the flag $fieldBraces.
  142. #
  143. # Note that the index names of the useBrace() array _define_ the valid 
  144. # field types recognized by the program.
  145. #
  146. array set useBrace {
  147.     address 1
  148.     annote 1
  149.     author 1
  150.     booktitle 1
  151.     chapter 0
  152.     crossref 1
  153.     edition 1
  154.     editor 1
  155.     howpublished 1
  156.     institution 1
  157.     journal 1
  158.     key 1
  159.     language 1
  160.     month 1
  161.     note 1
  162.     number 0
  163.     organization 1
  164.     pages 0
  165.     publisher 1
  166.     school 1
  167.     series 1
  168.     title 1
  169.     type 1
  170.     volume 0
  171.     year 0
  172.     isbn 1
  173.     customField 1
  174.     city 1
  175. }
  176.  
  177. set fieldNames [lsort [array names useBrace]]
  178. ###########################################################################
  179. # Default values for newly created fields
  180. #
  181. set defFldVal(language) "german"
  182.  
  183. set fieldDefs [lsort [array names defFldVal]]
  184.  
  185. ###########################################################################
  186. # BibTeX-mode mode definition
  187. ###########################################################################
  188.  
  189. set bibtexKeyWords $fieldNames
  190. regModeKeywords -e {%} -m {@} -c red -k blue Bib $bibtexKeyWords
  191. unset bibtexKeyWords
  192.  
  193. ###########################################################################
  194. # BibTeX Menu Definition.
  195. ###########################################################################
  196. proc bibtexMenu {} {}
  197.  
  198. proc bibtex {} {
  199.     global bibtexSig
  200.     set name [app::launchAnyOfThese {BIBt Vbib} bibtexSig]
  201.     switchTo [file tail $name]
  202. }
  203.  
  204. menu -n $bibtexMenu {
  205.     "bibtex"
  206.     "(-)"  
  207.     {menu -n Entries -p makeEntry {}
  208.     }
  209.     {menu -n Fields -p makeField {}
  210.     }
  211.     "(-)"
  212.     "selectEntry/B<U<B"
  213.     "nextEntry/N<U<B"
  214.     "prevEntry/P<U<B"
  215.     "formatEntry/L<U<B"
  216.     "copyCiteKey/C<U<B"
  217.     "(-)"
  218.     "searchEntries/M<U<B"
  219.     "searchFields/F<U<B"
  220.     {menu -n sortBy... -p bibSortProc {
  221.         "citeKey"
  222.         "firstAuthor,Year"
  223.         "lastAuthor,Year"
  224.         "year,FirstAuthor"
  225.         "year,LastAuthor"
  226.         }
  227.     }
  228.     {menu -n sortMarks... -p markSortProc {
  229.         "alphabetically"
  230.         "byPosition"
  231.         }
  232.     }
  233.     "(-)"
  234.     "countEntries"
  235.     "formatAllEntries"
  236.     "bibMakeIndex"
  237.     "bibMakeDatabase"
  238.     
  239. menu -n Entries -p makeEntry [concat $entryNames {
  240.     "(-)"
  241.     "customEntry"
  242. } ]
  243.  
  244. menu -n Fields -p makeField [concat $fieldNames {
  245.     "(-)"
  246.     "customField"
  247.     "multipleFields"
  248. } ]
  249.  
  250. ## 
  251.  # -------------------------------------------------------------------------
  252.  #   
  253.  # "bib_OpenFile" --
  254.  #  
  255.  #  Given a filename, and the directory of the base '.aux' file, try and
  256.  #  find the file.  If we don't succeed, pass the request onto the TeX
  257.  #  code.
  258.  # -------------------------------------------------------------------------
  259.  ##
  260. proc bib_OpenFile {filename {dir ""}} {
  261.     # look where base file was
  262.     if {![catch {openFileQuietly "${dir}:${filename}"}]} {
  263.         return
  264.     }
  265.     # look in bibtex inputs folder
  266.     global bibtexSig
  267.     if {![catch {openFileQuietly "[file dirname [nameFromAppl $bibtexSig]]:BibTeX inputs:${filename}"}]} {
  268.         return
  269.     } 
  270.     # look in all usual tex places
  271.     openTeXFile "$filename"
  272.     return
  273. }
  274.  
  275. ## 
  276.  # -------------------------------------------------------------------------
  277.  #   
  278.  # "bib_NoEntryExists" --
  279.  #  
  280.  #  No entry exists in the known .bib files.  Either add an entry, possibly
  281.  #  in a new bibliography file, or add a .bib file to those currently
  282.  #  searched.
  283.  # -------------------------------------------------------------------------
  284.  ##
  285. proc bib_NoEntryExists {item {basefile ""}} {
  286.     set basefile [bib_getBasefile $basefile]
  287.     set choice [prompt  \
  288.         "No entry '$item' exists.  What do you want to do?" \
  289.         "New entry" "Choices" \
  290.         "New entry" "New entry in new bibliography file" \
  291.         "Add .bib file to \\bibliography\{…\}" \
  292.         "Change original citation" \
  293.         "Search all bibliographies" ]
  294.     switch $choice {
  295.       "New entry" {
  296.           # need to pick a .bib file
  297.           set bibfile [bibPickBibliography 1 \
  298.               "Select a bibliography file to which to add an entry"]
  299.           openTeXFile $bibfile
  300.           global entryNames
  301.           bibFormatSetup
  302.           newEntry [listpick -p "Which type of entry?" $entryNames]
  303.           insertText $item
  304.           nextTabStop
  305.       }
  306.       "New entry in new bibliography file" {
  307.         set bibfile [putfile "Save new bibliography as…" ".bib"]
  308.         if {$bibfile == ""} {
  309.             error "No bibliography file selected."
  310.         } else {
  311.             new -n $bibfile
  312.         }        
  313.           global entryNames
  314.           bibFormatSetup
  315.           newEntry [listpick -p "Which type of entry?" $entryNames]
  316.           insertText $item
  317.           nextTabStop
  318.       }
  319.       "Add .bib file to \\bibliography\{…\}" {
  320.           bib_insertNewBibliography $basefile    
  321.       }
  322.       "Search all bibliographies" {
  323.           alertnote "Not yet implemented"
  324.       }
  325.       "Change original citation" {
  326.           bib_changeOriginalCitation $item $basefile
  327.       }
  328.       "Cancel" {
  329.           # nothing
  330.       }
  331.       }               
  332. }
  333.  
  334. proc bib_changeOriginalCitation {citation {basefile ""}} {
  335.       if {$basefile == ""} {set basefile [TeX_currentBaseFile]}
  336.       # find .aux and open base .tex/.ltx
  337.     if {[set proj [isWindowInFileset $basefile "tex"]] != ""} {
  338.         set files [texListFilesInFileSet $proj]
  339.     } else {
  340.         set files $basefile
  341.     }
  342.     set got "[grep $citation $files]\r"
  343.     if {[string first "; Line " $got] == [string last "; Line " $got]} {
  344.         # just one match
  345.         if ![regexp {∞([^\r\n]*)[\r\n]} $got dmy filename] {
  346.             alertnote "I couldn't find the original.  You probably have a\
  347.               multi-part document which you haven't made into a TeX fileset.\
  348.               Unless it's a fileset, I can't find the other files."
  349.             return
  350.         }
  351.         openFileQuietly $filename
  352.         eval select [searchInFile $filename $citation 1]
  353.         message "This is the original citation.  Change it, then re-run LaTeX and BibTeX."
  354.     } else {
  355.         grepsToWindow "* List of citations *" $got
  356.     }
  357. }
  358.  
  359. proc bib_getBasefile {{basefile ""}} {
  360.       if {$basefile == ""} {return [TeX_currentBaseFile]}
  361.       # find .aux and open base .tex/.ltx
  362.       set base [file root $basefile]
  363.       if [file exists ${base}.tex] {
  364.           return ${base}.tex
  365.       } elseif [file exists ${base}.ltx] {
  366.           return ${base}.ltx
  367.       } else {
  368.           alertnote "Base file with name '${base}.tex/ltx' not found." 
  369.         error ""
  370.       }                                   
  371. }
  372.  
  373. proc bib_insertNewBibliography {{basefile ""} {bibfile ""}} {
  374.       set basefile [bib_getBasefile $basefile]
  375.       openFileQuietly ${basefile}
  376.       
  377.       # find bibliography, position cursor and add
  378.     pushPosition
  379.       endOfBuffer
  380.       if [catch {set pos [search -s -f 0 -r 0 -m 0 "\\bibliography\{" [getPos]]}] {
  381.           # add the environment
  382.           set pos [search -s -f 0 "\\end\{document\}" [getPos]]
  383.           goto [lindex $pos 0]
  384.           set preinsert "\\bibliography\{"
  385.           set postinsert "\}\r\r"
  386.       } else {
  387.           set preinsert ""
  388.           set postinsert ","
  389.           goto [lindex $pos 1]
  390.       }
  391.       if {$bibfile == ""} {
  392.         set bibfile [bibPickBibliography 0 \
  393.             "Select a bibliography file to add"]
  394.     }
  395.       insertText "${preinsert}[lindex [split $bibfile "."] 0]${postinsert}"
  396.     message "press <Ctrl .> to return to original cursor position"
  397. }
  398.  
  399. # Used by bibPickBibliography to set a default in the listpick dialog
  400. # It's useful because you will often want to add a bunch of new items
  401. # in a row to the same bibliography.
  402. # NOTE: this is set by my code, not you.
  403. set Bib_defaultBib ""
  404.  
  405. ## 
  406.  # -------------------------------------------------------------------------
  407.  #     
  408.  # "bibPickBibliography" --
  409.  #    
  410.  #    Put    up a list-dialog so    the    user can select    a bibliography file    for
  411.  #    some action    (taken by the caller).    Can    also create    a new file if
  412.  #    desired.
  413.  # -------------------------------------------------------------------------
  414.  ##
  415. proc bibPickBibliography {{allowNew 1} {prompt "Pick a bibliography file"}} {
  416.     set biblist [bibListAllBibliographies]
  417.     if $allowNew {
  418.         lappend biblist {New file…}
  419.     }
  420.     global Bib_defaultBib
  421.     set bibfile [listpick -p $prompt -L $Bib_defaultBib $biblist]
  422.     if {$bibfile == ""} {
  423.         error "No bibliography file selected."
  424.     } elseif {$bibfile == "New file…" } {
  425.         set bibfile [putfile "Save new bibliography as…" ".bib"]
  426.         if {$bibfile == ""} {
  427.             error "No bibliography file selected."
  428.         } else {
  429.             set fout [open $bibfile w]
  430.             close $fout
  431.         }        
  432.     }
  433.     return [file tail [set Bib_defaultBib $bibfile]]
  434. }
  435.  
  436. ## 
  437.  # -------------------------------------------------------------------------
  438.  #     
  439.  # "bibListAllBibliographies" --
  440.  #    
  441.  #    Return all bibliographies on the search    path.  Optionally only return
  442.  #  those which are in a given .aux file.
  443.  # -------------------------------------------------------------------------
  444.  ##
  445. proc bibListAllBibliographies { {auxfile ""} } {
  446.     TeXEnsureSearchPathSet
  447.     global TeXSearchPath
  448.     set biblist {}
  449.     if {$auxfile == "" || [catch {set fid [open "$auxfile" r]}]} {
  450.         foreach d $TeXSearchPath {
  451.             eval lappend biblist [glob -nocomplain ${d}*.bib]
  452.         }
  453.     } else {
  454.         set bibs {}
  455.         # get list of bibs from .aux file
  456.         set cid [scancontext create]
  457.         scanmatch $cid {bibdata\{([^\}]*)\}} {
  458.             eval lappend bibs [split $matchInfo(submatch0) ","]
  459.         }
  460.         scanfile $cid $fid
  461.         close $fid
  462.         scancontext delete $cid
  463.         # find the full paths
  464.         foreach b $bibs {
  465.             foreach d $TeXSearchPath {
  466.                 if [file exists ${d}${b}.bib] {
  467.                     lappend biblist ${d}${b}.bib
  468.                     break
  469.                 }
  470.             }        
  471.         }
  472.     }
  473.     
  474.     return $biblist
  475. }
  476.  
  477. ## 
  478.  # -------------------------------------------------------------------------
  479.  #     
  480.  # "bibGotoEntry" --
  481.  #    
  482.  #    Look for a bib entry in    the    given list of files, or    if that    fails or
  483.  #    isn't given, look in all available bib files on    the    search path.
  484.  # -------------------------------------------------------------------------
  485.  ##
  486. proc bibGotoEntry {entry {biblist {}}} {
  487.     if ![catch {bib_GotoEntryFromIndex $entry}] {
  488.         return
  489.     }
  490.     if {[llength $biblist] && ![catch {bib_GotoEntry $entry $biblist 0}]} {
  491.         return
  492.     }
  493.     if ![catch {bib_GotoEntry $entry [bibListAllBibliographies]}] {
  494.         return
  495.     }
  496.     beep
  497.     error "Can't find entry '$entry' in the .bib file(s)"
  498. }
  499.  
  500. ## 
  501.  # -------------------------------------------------------------------------
  502.  #     
  503.  # "bib_GotoEntryFromIndex"    --
  504.  #    
  505.  #    Look in    the    bibIndex and find an entry very    quickly.
  506.  # -------------------------------------------------------------------------
  507.  ##
  508. proc bib_GotoEntryFromIndex {entry} {
  509.      set bibTopPat {@([a-zA-Z]+)[\{\(][     ]*}
  510.     global PREFS
  511.     # if it fails, but we succeed later, we will have the opportunity
  512.     # to rebuild the bibIndex
  513.     if [file exists "${PREFS}:bibIndex"] {
  514.         source "${PREFS}:bibIndex"
  515.         global bibIndex
  516.         foreach f [array names bibIndex] {
  517.             if [regexp "\[ \r\n\]$entry\[ \r\n\]" "$bibIndex($f)"] {
  518.                 openFileQuietly $f
  519.                 set p [search -s -f 1 -r 1 $bibTopPat$entry 0]
  520.                 eval select $p
  521.                 refresh
  522.                 eval select $p
  523.                 unset bibIndex
  524.                 return
  525.             }
  526.         }
  527.         unset bibIndex
  528.     }
  529.     error "Entry '$entry' not found in bibIndex"
  530. }
  531.  
  532. ## 
  533.  # -------------------------------------------------------------------------
  534.  #     
  535.  # "bib_FindAllEntries"    --
  536.  #    
  537.  #    Find all entries with a    given prefix, optionally attaching the titles
  538.  #    of the entries (this requires a    bibDatabase    file to    be setup).    Used
  539.  #    by TeX citation    completions: \cite{Darley<cmd-Tab>
  540.  # -------------------------------------------------------------------------
  541.  ##
  542. proc bib_FindAllEntries {eprefix {withtitles 1}} {
  543.     global PREFS 
  544.     set matches {}
  545.     if $withtitles {
  546.         if ![file exists "${PREFS}:bibDatabase"] {
  547.             if {[askyesno "No bibDatabase exists, shall I make one?"]=="yes"} {
  548.                 bibMakeDatabase
  549.             } else {
  550.                 error "No bib database exists"
  551.             }
  552.         }
  553.         set cid [scancontext create]
  554.         scanmatch $cid "^${eprefix}" {
  555.             lappend matches $matchInfo(line)
  556.         }
  557.         set fid [open "${PREFS}:bibDatabase" r]
  558.         scanfile $cid $fid
  559.         close $fid
  560.         scancontext delete $cid    
  561.     } else {
  562.         if ![file exists "${PREFS}:bibIndex"] {
  563.             if {[askyesno "No bibIndex exists, shall I make one?"]=="yes"} {
  564.                 bibMakeIndex
  565.             } else {
  566.                 error "No bib index exists"
  567.             }
  568.         }
  569.         global bibIndex
  570.         source "${PREFS}:bibIndex"
  571.         foreach f [array names bibIndex] {
  572.             if { [set matched [univ::modeListCompletions $eprefix "bibIndex(${f})"]] != 0 } {
  573.                 eval lappend matches $matched
  574.             }
  575.         }
  576.         unset bibIndex
  577.     }
  578.     return $matches    
  579. }
  580.  
  581. ## 
  582.  # -------------------------------------------------------------------------
  583.  #     
  584.  # "bib_GotoEntry" --
  585.  #    
  586.  #    Find a bib entry in    one    of the given list of files,    and    signal an
  587.  #    error if the entry isn't found.     I think this is the quickest way.
  588.  # -------------------------------------------------------------------------
  589.  ##
  590. proc bib_GotoEntry {entry biblist {rebuild 1}} {
  591.      set bibTopPat {@([a-zA-Z]+)[\{\(][     ]*}
  592.      set cid [scancontext create]
  593.      scanmatch $cid $bibTopPat$entry {
  594.          set found "$matchInfo(offset)"
  595.      }
  596.      set found ""
  597.     foreach f $biblist {
  598.         message "Searching [file tail $f]…"
  599.         if {![catch {set fid [open $f]}]} {
  600.             scanfile $cid $fid
  601.             close $fid
  602.             if {$found != ""} {
  603.                 openFileQuietly $f
  604.                 goto $found
  605.                 refresh
  606.                 select $found [nextLineStart $found]
  607.                 scancontext delete $cid
  608.                 global BibmodeVars
  609.                 # make the index since it was obviously out of date                
  610.                 if {$rebuild == 1 && ($BibmodeVars(bibAutoIndex) == 2 || [askyesno "The bibIndex seems to be out of date.  Rebuild?"]=="yes")} {
  611.                     bibMakeIndex
  612.                 }
  613.                 return
  614.             }    
  615.         }
  616.     }
  617.     scancontext delete $cid
  618.     error "Entry '$entry' not found."
  619. }
  620.  
  621.  
  622. ## 
  623.  # -------------------------------------------------------------------------
  624.  #     
  625.  # "bibMakeIndex" --
  626.  #    
  627.  #    Build the bibIndex file    which allows for very fast lookup of bib
  628.  #    entries.
  629.  # -------------------------------------------------------------------------
  630.  ##
  631. proc bibMakeIndex {} {
  632.     global PREFS 
  633.     set bibTopPat2 {^[     ]*@([a-zA-Z]+)[\{\(][     ]*([^=,     ]+)}    
  634.      set cid [scancontext create]
  635.      # this will actually mark strings as well
  636.      scanmatch $cid $bibTopPat2 {
  637.          if {[string tolower $matchInfo(submatch0)] != "string"} {
  638.              lappend found $matchInfo(submatch1)
  639.          }
  640.      }
  641.      set bout [open "${PREFS}:bibIndex" w]
  642.      puts $bout "# Bibliography index file for quick reference lookup"
  643.      puts $bout "# Created on [mtime [now]]"
  644.     foreach f [bibListAllBibliographies] {
  645.         set found {}
  646.         puts $bout "set \"bibIndex($f)\" \{"
  647.         message "Scanning [file tail $f]…"
  648.         if {![catch {set fid [open $f]}]} {
  649.             scanfile $cid $fid
  650.             close $fid
  651.         }
  652.         # we sort so we can search it efficiently for all entries with
  653.         # a given prefix.
  654.         puts $bout " [lsort $found] "
  655.         puts $bout "\}"
  656.     }
  657.     close $bout
  658.     scancontext delete $cid
  659.     message "bibIndex creation complete"
  660. }
  661.  
  662. ## 
  663.  # -------------------------------------------------------------------------
  664.  #     
  665.  # "bibMakeDatabase" --
  666.  #    
  667.  #    Build the bibDatabase which    allows speedy completion of    citations and
  668.  #    contains titles, so    that you can pick the correct completion easily.
  669.  # -------------------------------------------------------------------------
  670.  ##
  671. proc bibMakeDatabase {} {
  672.      set bibTopPat {@([a-zA-Z]+)[\{\(][     ]*}
  673.     global PREFS
  674.     set bdatout [open "${PREFS}:bibDatabase" w]
  675.      puts $bdatout "# Bibliography database file for quick reference lookup"
  676.      puts $bdatout "# Created on [mtime [now]]"
  677.     # if it fails, but we succeed later, we will have the opportunity
  678.     # to rebuild the bibIndex
  679.     foreach f [bibListAllBibliographies] {
  680.         message "Scanning ${f}…"
  681.         openFileQuietly $f
  682.         set p 0
  683.         while {![catch {search -s -f 1 -r 1 $bibTopPat $p} epos]} {
  684.             set p [lindex $epos 1]
  685.             set np [nextLineStart $p]
  686.             set entry [string trim [getText $p $np] "\{\( \t\r,"]
  687.             if ![catch {search -s -f 1 -r 1 {title[ \t]*=.*,[ \t]*\r} $np} epos] {
  688.                 set title [eval getText $epos]
  689.                 regsub -all "\[\r\t\]+" $title { } title
  690.                 set title [string range $title [string first "=" $title] end]
  691.                 set title [string trim $title " =\{\}\","]
  692.                 puts $bdatout "$entry \{$title\}"
  693.                 set p [lindex $epos 1]
  694.             }
  695.         }   
  696.         killWindow
  697.     }
  698.     close $bdatout
  699. }
  700.  
  701.  
  702. ###########################################################################
  703. # Menu command procs
  704. ###########################################################################
  705.         
  706. proc makeField {menu item} {
  707.     global fieldNames
  708.     bibFormatSetup
  709.     
  710.     if {$item == "multipleFields"} {
  711.         set flds [listpick -l -L {author year} -p "Pick desired fields:" $fieldNames]
  712.         if {[llength flds]} {
  713.             set lines {}
  714.             foreach fld $flds {
  715.                 append lines [newField $fld]
  716.             }
  717.         } else {
  718.             return
  719.         }
  720.     } else {
  721.         set lines [newField $item]
  722.     }
  723.     
  724.     set pos0 [nextLineStart [getPos]]
  725.     goto $pos0
  726.     elec::Insertion $lines
  727. }
  728.  
  729. proc makeEntry {menu item} {
  730.     bibFormatSetup
  731.     newEntry $item
  732. }
  733.  
  734. ###########################################################################
  735. #  Return the bounds of the bibliographic entry surrounding the current 
  736. #  position.
  737. #
  738. proc getEntry {pos} {
  739.     
  740.      set pos1 [search -f 0 -r 1 -n -s {[     ]*@[a-zA-Z]*[\{\(]} $pos ]
  741.     if {$pos1 == ""} {
  742.         set begPos [nextLineStart $pos]
  743.         set endPos $begPos
  744.     } else {
  745.         set begPos [lineStart [lindex $pos1 0]]
  746.         set pos0 [lindex $pos1 1]
  747.         set openBrace [getText [expr $pos0-1] $pos0 ]
  748.         if {[catch {matchIt $openBrace $pos0} pos1]} {
  749.             alertnote "There seems to be a badly delimited field in here.  Are entry and field delimiters set correctly?"
  750.             goto $begPos
  751.             error "Can't find close brace"
  752.         } else {
  753.             set endPos [nextLineStart $pos1]
  754.         }
  755.     }
  756.     return [list $begPos $endPos]
  757. }
  758.  
  759. ###########################################################################
  760. #  Advance to the next bibliographic entry.
  761. #
  762. proc nextEntry {} {
  763.     global bibTopPat bibTopPat1 bibTopPat2
  764. #     set topPat {[     ]*@([a-zA-Z]+)[\{\(]}
  765.     
  766.     set pos0 [lindex [getEntry [getPos]] 1]
  767.     set nextPos [nextLineStart $pos0]
  768.     
  769.     while {![catch {search -f 1 -r 1 -s $bibTopPat $pos0} pos]} {
  770.         regexp $bibTopPat [eval getText $pos] mtch type
  771.         if {$type != "string"} {
  772.             set nextPos [lindex $pos 0]
  773.             break
  774.         } else {
  775.             set pos0 [nextLineStart [lindex $pos 1]]
  776.         }
  777.     }
  778.     goto $nextPos
  779. }
  780.  
  781. ###########################################################################
  782. #  Go back to the previous bibliographic entry.
  783. #
  784. proc prevEntry {} {
  785.     global bibTopPat bibTopPat1 bibTopPat2
  786. #     set topPat {[     ]*@([a-zA-Z]+)[\{\(]}
  787.     
  788.     set pos0 [lindex [getEntry [getPos]] 0]
  789.     if {$pos0 > 0} {
  790.         set nextPos $pos0
  791.         incr pos0 -1
  792.         while {![catch {search -f 0 -r 1 -s $bibTopPat $pos0} pos]} {
  793.             regexp $bibTopPat [eval getText $pos] mtch type
  794.             if {$type != "string"} {
  795.                 set nextPos [lindex $pos 0]
  796.                 break
  797.             } else {
  798.                 set pos0 [lineStart [lindex $pos 0]]
  799.                 if {$pos0 == 0} {break}
  800.                 incr pos0 -1
  801.             }
  802.         }
  803.         goto $nextPos
  804.     }
  805. }
  806.  
  807. ###########################################################################
  808. #  Select (highlight) the current bibliographic entry.
  809. #
  810. proc selectEntry {} {
  811.     set pos [getEntry [getPos]]
  812.     select [lindex $pos 0] [lindex $pos 1]
  813. }
  814.  
  815. ###########################################################################
  816. #  Put the cite-key of the current entry on the clipboard.
  817. #
  818. proc copyCiteKey {} {
  819.     global bibTopPat2
  820.     set limits [getEntry [getPos]]
  821.     set top [lindex $limits 0]
  822.     set bottom [lindex $limits 1]
  823.     if {[regexp -indices $bibTopPat2 [getText $top $bottom] allofit type citekey]} {
  824.         select [expr $top+[lindex $citekey 0]] [expr $top+[lindex $citekey 1]+1]
  825.         copy
  826.         message "Copied \"[getSelect]\""
  827.     } 
  828. }
  829.  
  830. ###########################################################################
  831. #  Create a new bibliographic entry with its required fields.
  832. #
  833. proc newEntry {entryName} {    
  834.     global  entryNames customEntries fieldNames rqdFld optFld myFld defFldVal
  835.     global bibOpenEntry bibCloseEntry BibmodeVars
  836.     goto [lindex [getEntry [getPos]] 1]
  837.     if {$entryName == "customEntry"} {
  838.         set lines "@••$bibOpenEntry••,\r"
  839.         set theFields [listpick -l -L {author} -p "Pick desired fields:" $fieldNames]
  840.     } else {
  841.         set lines "@${entryName}$bibOpenEntry••,\r"
  842.         if {[lsearch -exact $customEntries $entryName] >= 0 && [llength $myFld($entryName)]} {
  843.             set theFields $myFld($entryName)
  844.         } elseif {[lsearch -exact $entryNames $entryName] >= 0} {
  845.             set theFields $rqdFld($entryName)
  846.         } else {
  847.             set theFields {}
  848.         }
  849.     }
  850.     set nmlen 0
  851.     foreach field $theFields {
  852.         set len [string length $field]
  853.         if {$len > $nmlen} {set nmlen $len}        
  854.     }
  855.     set theTop [lineStart [getPos]]
  856.     foreach field $theFields {
  857.         catch {append lines [newField $field $nmlen]}
  858.     }
  859.     append lines "$bibCloseEntry\r"
  860.     elec::Insertion $lines
  861. }
  862.  
  863. ###########################################################################
  864. #  Create a new field within the current bibliographic entry
  865. #
  866. proc newField {fieldName {nmlen 0}} {    
  867.     global fieldNames useBrace bibOpenQuote bibCloseQuote bibIndent
  868.     global fieldDefs defFldVal
  869.     set spc "                   "
  870.     if {[lsearch -exact $fieldNames $fieldName] >= 0} {
  871.         set needBraces $useBrace($fieldName)
  872.     } else {
  873.         set needBraces 1
  874.     }
  875.     
  876.     if {[lsearch -exact $fieldDefs $fieldName] >= 0} {
  877.         set val $defFldVal($fieldName)
  878.     } else {
  879.         set val "••"
  880.     }
  881.     
  882.     if {$nmlen} {
  883.         set pad [string range $spc 1 [expr $nmlen - [string length $fieldName]]]
  884.     } else {
  885.         set pad ""
  886.     }            
  887.     if {$needBraces || $fieldName == "customField"} {
  888.         set result "$bibIndent$fieldName =$pad ${bibOpenQuote}${val}${bibCloseQuote},\r"
  889.     } else {
  890.         set result "$bibIndent$fieldName =$pad $val,\r"
  891.     }    
  892.     return $result
  893. }
  894.  
  895. proc bibFormatSetup {} {
  896.     global bibOpenQuote bibCloseQuote bibIndent BibmodeVars
  897.     global bibOpenEntry bibCloseEntry bibAbbrevs
  898.     bibFieldDelims
  899.     bibEntryDelims
  900.     set bibIndent $BibmodeVars(indentString)
  901.     regsub {\\t} $bibIndent {    } bibIndent
  902.     set bibAbbrevs [listStrings]
  903.     foreach abbrev $BibmodeVars(stdAbbrevs) {
  904.         lappend bibAbbrevs [string tolower $abbrev]
  905.     }
  906. }
  907.  
  908. ###########################################################################
  909. #  Find all entries that match a given regular expression and copy them to 
  910. #  a new buffer.
  911. #
  912. proc searchEntries {} {
  913.     if [catch {prompt "Regular expression:" ""} reg] return
  914.     if {![string length $reg]} return
  915.     set reg ^.*$reg.*$
  916.     
  917.     set matches [findEntries $reg]
  918.     if {[llength $matches] >0} {
  919.         writeEntries $matches 0
  920.     } else {
  921.         message "No matching entries were found"
  922.     }
  923. }
  924.  
  925. ###########################################################################
  926. #  Find all entries in which the indicated field matches a given regular 
  927. #  expression and copy them to a new buffer.  
  928. #
  929. proc searchFields {} {
  930.     global fieldNames
  931.     if {[catch {eval prompt {{Field name:}} "author" {Fields} $fieldNames} fld]} return
  932.     if {![string length $fld]} return
  933.  
  934.     if {[catch {prompt "Regular expression:" ""} reg]} return
  935.     if {![string length $reg]} return
  936.  
  937.     set matches [findEntries $reg]
  938.     if {[llength $matches] == 0} {
  939.         return "No matching entries were found"
  940.     }
  941.     
  942.     set vals {}
  943.     foreach hit $matches {
  944.         set pos [lindex $hit 1]
  945.         set top [lindex $hit 2] 
  946.         set bottom [lindex $hit 3]
  947.         while {[set failure [expr {[getFldName $pos $top] != $fld}]]  && 
  948.             ![catch {search -f 1 -r 1 -i 1 -m 0 -l $bottom -s -- $reg $pos} mtch]} {
  949.             set pos [lindex $mtch 1]
  950.         }
  951.         if {!$failure} { lappend vals [list $top $bottom] }
  952.     }
  953.     
  954.     if {[llength $vals] >0} {
  955.         writeEntries $vals 0
  956.     } else {
  957.         message "No matching entries were found"
  958.     }
  959.     
  960. }
  961.  
  962. ###########################################################################
  963. # Sort all of the entries based on one of various criteria.
  964. #
  965. proc bibSortProc {menu item} {
  966.     if {$item == "citeKey"} {
  967.         sortByCiteKey
  968.     } elseif  {$item == "firstAuthor,Year"} {
  969.         sortByAuthors 0 0
  970.     } elseif  {$item == "lastAuthor,Year"} {
  971.         sortByAuthors 1 0
  972.     } elseif  {$item == "year,FirstAuthor"} {
  973.         sortByAuthors 0 1
  974.     } elseif  {$item == "year,LastAuthor"} {
  975.         sortByAuthors 1 1
  976.     }
  977. }
  978.  
  979. ###########################################################################
  980. # Sort the file marks. (These operations are also available under the
  981. # "Search:NamedMarks" menu)
  982. #
  983. proc markSortProc {menu item} {
  984.     if {$item == "alphabetically"} {
  985.         sortMarksFile
  986.     } elseif  {$item == "byPosition"} {
  987.         orderMarks
  988.     }
  989. }
  990.  
  991. ###########################################################################
  992. # Sort all of the entries in the file alphabetically by author.
  993. #
  994. proc sortByAuthors {{lastAuthorFirst 0} {yearFirst 0}} {
  995.     global bibTopPat bibTopPat1 bibTopPat2 BibmodeVars
  996.     set bibSegStr $BibmodeVars(segregateStrings)
  997.     
  998.     set matches [findEntries $bibTopPat]
  999.     set crossrefs [listCrossrefs]
  1000.     set strings [listStrings]
  1001.     
  1002.     set vals {}
  1003.     set others {}
  1004.     set refs {}
  1005.     set strs {}
  1006.     
  1007.     set beg [maxPos]
  1008.     set end 0
  1009.     
  1010.     foreach hit $matches {
  1011.         set pos [lindex $hit 1]
  1012.         set top [lindex $hit 2] 
  1013.         set bottom [lindex $hit 3]
  1014.         set entry [getText $top $bottom]
  1015.         regsub -all "\[\n\r\]+" $entry { } entry
  1016.         regsub -all "\[     \]\[     \]+" $entry { } entry
  1017.         regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  1018.         if {[regexp $bibTopPat1 $entry allofit citeKey]} {
  1019.             set citeKey [string tolower $citeKey]
  1020.             set keyExists 1
  1021.         } else {
  1022.             set citekey ""
  1023.             set keyExists 0
  1024.         }
  1025.         
  1026.         if {$keyExists && [lsearch -exact $crossrefs $citeKey] >= 0} {
  1027.             lappend refs [list $pos $top $bottom]
  1028.         } elseif {$bibSegStr && $keyExists && [lsearch -exact $strings $citeKey] >= 0} {
  1029.             lappend strs [list $citeKey $top $bottom]        
  1030.         } else {
  1031.             if {![catch {getFldValue $entry author} fldval]} {
  1032.                 if {[catch {getFldValue $entry year} year]} { set year 9999 }
  1033.                 lappend vals [list [authSortKey $fldval $lastAuthorFirst $year $yearFirst] $top $bottom]
  1034.             } else {
  1035.                 lappend others [list $pos $top $bottom]
  1036.             }
  1037.         }
  1038.         if {$top < $beg} {set beg $top}
  1039.         if {$bottom > $end} {set end $bottom}
  1040.     }
  1041.     
  1042.     if {$bibSegStr} {
  1043.         set result [concat $strs $others [lsort $vals] $refs]
  1044.     } else {
  1045.         set result [concat $others [lsort $vals] $refs]
  1046.     }
  1047.     
  1048.     if {[llength $result] >0} {
  1049.         writeEntries $result 1 $beg $end
  1050.     } else {
  1051.         message "No results of author sort !!??"
  1052.     }
  1053. }
  1054.  
  1055. ###########################################################################
  1056. # Return a list of the cite-keys of all cross-referenced entries.
  1057. #
  1058. proc listStrings {} {
  1059.     global bibTopPat bibTopPat1 bibTopPat2
  1060.     set matches [findEntries {^[    ]*@string *[\{\(]} 0]
  1061.  
  1062.     message "scanning for @strings…"
  1063.     foreach hit $matches {
  1064.         set top [lindex $hit 2] 
  1065.         set bottom [lindex $hit 3]
  1066.         set entry [getText $top $bottom]
  1067.         regsub -all "\[\n\r\]+" $entry { } entry
  1068.         regsub -all "\[     \]\[     \]+" $entry { } entry
  1069.         regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  1070.         regexp $bibTopPat1 $entry allofit citekey
  1071.         set citekey [string tolower $citekey]
  1072.         if {[catch {incr strings($citekey)} num]} {
  1073.             set strings($citekey) 1
  1074.         }
  1075.     }
  1076.     if {[catch {lsort [array names strings]} res]} {
  1077.         set res {}
  1078.     }
  1079.     message ""
  1080.     return $res
  1081. }
  1082.  
  1083. ###########################################################################
  1084. # Return a list of the cite-keys of all cross-referenced entries.
  1085. #
  1086. proc listCrossrefs {} {
  1087.     set matches [findEntries {crossref}]
  1088.     catch {unset crossrefs}
  1089.  
  1090.     message "scanning for crossrefs…"
  1091.     foreach hit $matches {
  1092.         set top [lindex $hit 2] 
  1093.         set bottom [lindex $hit 3]
  1094.         set entry [getText $top $bottom]
  1095.         regsub -all "\[\n\r\]+" $entry { } entry
  1096.         regsub -all "\[     \]\[     \]+" $entry { } entry
  1097.         regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  1098.         if {![catch {getFldValue $entry crossref} fldval]} {
  1099.             set fldval [string tolower $fldval]
  1100.             if {[catch {incr crossref($fldval)} num]} {
  1101.                 set crossrefs($fldval) 1
  1102.             }
  1103.         }
  1104.     }
  1105.     if {[catch {lsort [array names crossrefs]} res]} {
  1106.         set res {}
  1107.     }
  1108.     message ""
  1109.     return $res
  1110. }
  1111.  
  1112. ###########################################################################
  1113. # Create a sort key from an author list.  When sorting entries by author, 
  1114. # performing the sort using keys should be faster than reparsing the author 
  1115. # lists for every comparison (the old method :-( ).
  1116. #
  1117. proc authSortKey {authList lastAuthorFirst {year {}} {yearFirst 0}} {
  1118.     global BibmodeVars
  1119.     set pat1 {\\.\{([A-Za-z])\}}
  1120.     set pat2 {\{([^\{\}]+) ([^\{\}]+)\}}
  1121.  
  1122. # Remove enclosing braces, quotes, or whitespace
  1123.     set auths %[string trim $authList {{}"     }]&
  1124. # Remove TeX codes for accented characters
  1125.     regsub -all $pat1 $auths {\1} auths
  1126. # Concatenate strings enclosed in braces
  1127.     while {[regsub -all $pat2 $auths {{\1\2}} auths]} {}
  1128. # Remove braces (curly and square)
  1129.     regsub -all {[][\{\}]} $auths {} auths
  1130. #    regsub -all {,} $auths { ,} auths
  1131. # Replace 'and's with begin-name/end-name delimiters
  1132.     regsub -all {[     ]and[     ]} $auths { \&% } auths
  1133. # Put last name first in name fields without commas
  1134.     regsub -all {%([^\&,]+) ([^\&, ]+) *\&} $auths {%\2,\1\&} auths
  1135. # Remove begin-name delimiters
  1136.     regsub -all {%} $auths {} auths
  1137. # Remove whitespace surrounding name separators
  1138.     regsub -all {[     ]*\&[     ]*} $auths {\&} auths
  1139. # Replace whitespace separating words with shrieks 
  1140.     regsub -all {[     ,]+} $auths {!} auths
  1141. # If desired, move last author to head of sort key
  1142.     if {$lastAuthorFirst} {
  1143.         regsub {(.*)&([^&]+)&?$} $auths {\2\&\1} auths
  1144.     }
  1145. # If provided, sort by year (descending order) as well
  1146.     regsub {^[^0-9]*([0-9]*).*$} $year {\1} year
  1147.     if {$year != {}} {
  1148.         if {$BibmodeVars(descendingYears)} { catch {set year [expr 9999-$year]} }
  1149.         if {$yearFirst} {
  1150.             set auths "$year&$auths"
  1151.         } else {        
  1152.             regsub {^([^&]+)(&?)} $auths "\\1\\&${year}\\2" auths
  1153.         }
  1154.     }
  1155.         
  1156.     return $auths
  1157. }
  1158.  
  1159. ###########################################################################
  1160. # Sort all of the entries in the file alphabetically by their cite-keys.
  1161. #
  1162. proc sortByCiteKey {} {
  1163.     global bibTopPat bibTopPat1 bibTopPat2 BibmodeVars
  1164.     set bibSegStr $BibmodeVars(segregateStrings)
  1165.     
  1166.     set matches [findEntries $bibTopPat]
  1167.     set crossrefs [listCrossrefs]
  1168.     set strings [listStrings]
  1169.  
  1170.     set begEntries [maxPos]
  1171.     set endEntries 0
  1172.     
  1173.     set strs {}
  1174.     set vals {}
  1175.     set refs {}
  1176.         
  1177.     foreach hit $matches {
  1178.         set beg [lindex $hit 0]
  1179.         set end [lindex $hit 1]
  1180.         set top [lindex $hit 2] 
  1181.         set bottom [lindex $hit 3]
  1182.         if {[regexp $bibTopPat1 [getText $top $bottom] allofit citekey]} {
  1183.             set citekey [string tolower $citekey]
  1184.             set keyExists 1
  1185.         } else {
  1186.             set citekey "000000$beg"
  1187.             set keyExists 0
  1188.         }
  1189.         
  1190.         if {$keyExists && [lsearch -exact $crossrefs $citekey] >= 0} {
  1191.             lappend refs [list $top $top $bottom]
  1192.         } elseif {$keyExists && $bibSegStr && [lsearch -exact $strings $citekey] >= 0} {
  1193.             lappend strs [list $citekey $top $bottom]        
  1194.         } else {
  1195.             lappend vals [list $citekey $top $bottom]
  1196.         }
  1197.  
  1198.         if {$top < $begEntries} {set begEntries $top}
  1199.         if {$bottom > $endEntries} {set endEntries $bottom}
  1200.     }
  1201.  
  1202.     if {$bibSegStr} {
  1203.         set result [concat $strs [lsort $vals] $refs]
  1204.     } else {
  1205.         set result [concat [lsort $vals] $refs]
  1206.     }
  1207.     
  1208.     if {[llength $result] >0} {
  1209.         writeEntries $result 1 $begEntries $endEntries
  1210.     } else {
  1211.         message "No results of cite-key sort !!??"
  1212.     }
  1213. }
  1214.  
  1215. ###########################################################################
  1216. # Search for all entries matching a given regular expression.  The results
  1217. # are returned in a list, each element of which is a list of four integers:
  1218. # the beginning and end of the matching entry and the beginning and end of
  1219. # the matching string.  Adapted from "matchingLines" in "misc.tcl".
  1220. #
  1221. proc findEntries {reg {casesen 1}} {
  1222.     if {![string length $reg]} return
  1223.     
  1224.     set pos 0   
  1225.     set result {}                             
  1226.     while {![catch {search -f 1 -r 1 -m 0 -i $casesen -s $reg $pos} mtch]} {
  1227.         set entry [getEntry [lindex $mtch 0]]
  1228.         lappend result [concat $mtch $entry]
  1229.         set pos [lindex $entry 1]
  1230.     }
  1231.     return $result
  1232. }
  1233.  
  1234. ###########################################################################
  1235. #  Return a list containing the data for the current entry, indexed by
  1236. #  the parameter names, e.g., "author", "year", etc.  Index names for the 
  1237. #  entry type and cite-key are "type" and "citekey". 
  1238. #
  1239. proc getFields {pos} {
  1240.      global bibTopPat bibTopPat1 bibTopPat2 bibTopPat3
  1241.     set fldPat {[     ]*([a-zA-Z]+)[     ]*=[     ]*}
  1242.  
  1243.     set limits [getEntry $pos]
  1244.     set top [lindex $limits 0]
  1245.     set bottom [lindex $limits 1]
  1246.     
  1247.     set entry [getText $top $bottom]
  1248.     regsub -all "\[\n\r\]+" $entry { } entry
  1249.     regsub -all "\[     \]\[     \]+" $entry { } entry
  1250. #
  1251.     regsub {[,     ]*[\)\}][     ]*$} $entry { } entry
  1252.  
  1253.     if {[regexp -indices $bibTopPat2 $entry mtch theType theKey ]} {
  1254.         set key [string range $entry [lindex $theKey 0] [lindex $theKey 1]]
  1255.         set theRest [expr 1 + [lindex $mtch 1]]
  1256.     } elseif {[regexp -indices $bibTopPat3 $entry mtch theType aField]} {
  1257.         set key {}
  1258.         set theRest [lindex $aField 0]
  1259.     } else {
  1260.         error "Invalid entry"
  1261.     }
  1262.     lappend names type
  1263.     set type [string tolower [string range $entry [lindex $theType 0] [lindex $theType 1]]]
  1264.     lappend data [list $type]
  1265.  
  1266.     lappend names citekey
  1267.     lappend data $key
  1268.     
  1269.     set entry ",[string range $entry $theRest end]"
  1270.     set fldPat {,[     ]*([^ =,]+)[     ]*=[     ]*}
  1271.     set name {}
  1272.     while {[regexp -indices $fldPat $entry mtch sub1]} {
  1273.         set nextName [string range $entry [lindex $sub1 0] [lindex $sub1 1]]
  1274.         lappend names [string tolower $nextName]
  1275.         if {$name != ""} { 
  1276.             set prevData [string range $entry 0 [expr [lindex $mtch 0]-1]]
  1277.             lappend data [breakIntoLines [bibFieldData $prevData]]
  1278.         }    
  1279.         set name $nextName
  1280.         set entry [string range $entry [expr [lindex $mtch 1]+1] end]
  1281.     }
  1282.  
  1283.     lappend data [breakIntoLines [bibFieldData $entry]]
  1284.     
  1285.     return [list $names $data]
  1286. }
  1287.  
  1288. proc bibFieldData {text} {
  1289.     set text [string trim $text {     ,#}]
  1290.     set text1 [string trim $text {\{\}\"     }]            
  1291.     
  1292.     if {[string match {*[\{\}\"]*} $text1]} {
  1293.         set words [parseWords $text]
  1294.         if {[llength $words]==1} {
  1295.             regsub {^[\{\"\']} $text {} text
  1296.             regsub {[\}\"\']$} $text {} text
  1297.         }
  1298.     } else {
  1299.         set text $text1            
  1300.     }
  1301.     return $text
  1302. }
  1303.  
  1304.  
  1305. ###########################################################################
  1306. # Extract the data from the indicated field of an entry, which is passed 
  1307. # as a single string.  This version tries to be completely general, 
  1308. # allowing nested braces within data fields and ignoring escaped 
  1309. # delimiters.  (derived from proc getField).
  1310. #
  1311. proc getFldValue {entry fldname} {
  1312.     set fldPat "\[     \]*${fldname}\[     \]*=\[     \]*"
  1313.     set fldPat2 {,[     ]*([^ =,]+)[     ]*=[     ]*}
  1314.     set slash "\\"
  1315.     set qslash "\\\\"
  1316.     
  1317.     set ok [regexp -indices -nocase $fldPat $entry mtch]
  1318.     if {$ok} {
  1319.         set pos [expr [lindex $mtch 1] + 1]
  1320.         set entry [string range $entry $pos end]
  1321.         
  1322.         if {[regexp -indices $fldPat2 $entry mtch sub1]} {
  1323.             set entry [string range $entry 0 [expr [lindex $mtch 0]-1]]
  1324.         } 
  1325.         set fld [bibFieldData $entry]
  1326.         
  1327.         return $fld
  1328.         
  1329.     } else {
  1330.         error "field not found"
  1331.     }
  1332. }
  1333.  
  1334. ###########################################################################
  1335. # Parse the entry around position "pos" and rewrite it to the original 
  1336. # buffer in a canonical format
  1337. #
  1338. proc formatEntry {} {
  1339.     global useBrace bibOpenQuote bibCloseQuote 
  1340.     global bibOpenEntry bibCloseEntry bibIndent
  1341.     set spc "                           "
  1342.     
  1343.     bibFormatSetup
  1344.     
  1345.     set pos [getPos]
  1346.     set limits [getEntry $pos]
  1347.     set top [lindex $limits 0]
  1348.     set bottom [lindex $limits 1]
  1349.     
  1350.     if {![catch {bibFormatEntry $pos} result]} {
  1351.         if {$result != [getText $top $bottom]} {
  1352.             replaceText $top $bottom $result
  1353.         } 
  1354.         goto $top 
  1355.         nextEntry
  1356.     } else {
  1357.         message "Couldn't format this entry for some reason"
  1358.     }
  1359. }
  1360.  
  1361. ###########################################################################
  1362. # Parse the entry around position "pos" and rewrite it to the original 
  1363. # buffer in a canonical format
  1364. #
  1365. proc formatAllEntries {} {
  1366.     global useBrace bibOpenQuote bibCloseQuote 
  1367.     global bibOpenEntry bibCloseEntry bibIndent
  1368.     set spc "                           "
  1369.     
  1370.     bibFormatSetup
  1371.     
  1372.     # This little dance handles the case that the first 
  1373.     # entry starts on the first line
  1374.     #
  1375.     set hit [getEntry [getPos]]
  1376.     if {[lindex $hit 0] == [lindex $hit 1]} {
  1377.         nextEntry
  1378.         set hit [getEntry [getPos]]
  1379.     }
  1380.     
  1381.     while {[getPos] < [lindex $hit 1]} {
  1382.         set top [lindex $hit 0] 
  1383.         set bottom [lindex $hit 1]
  1384.         
  1385.         if {![catch {bibFormatEntry $top} result]} {
  1386.             set oldEntry [getText $top $bottom]
  1387.             if {$result != $oldEntry} {
  1388.                 deleteText $top $bottom 
  1389.                 insertText $result
  1390.             } 
  1391.         }
  1392.         goto $top
  1393.         nextEntry
  1394.         set hit [getEntry [getPos]]
  1395.     }
  1396. }
  1397.  
  1398. ###########################################################################
  1399. # Parse the entry around position "pos" and rewrite it in a canonical format.
  1400. # The formatted entry is returned.
  1401. #
  1402. proc bibFormatEntry {pos} {
  1403.     global useBrace bibOpenQuote bibCloseQuote 
  1404.     global bibOpenEntry bibCloseEntry bibIndent
  1405.     global rqdFld optFld BibmodeVars bibAbbrevs
  1406.     set spc "                           "
  1407.     #    
  1408.     #    note: calling proc must call "bibFormatSetup" before calling "bibFormatEntry"
  1409.     #
  1410.     set limits [getEntry $pos]
  1411.     set top [lindex $limits 0]
  1412.     set bottom [lindex $limits 1]
  1413.  
  1414.     if {[catch {getFields $pos} flds]} {
  1415.         error "bibFormatEntry: Getflds couldn't find any"
  1416.     }
  1417.     
  1418.     set names [lindex $flds 0]
  1419.     set vals [lindex $flds 1]
  1420.     set nfld [llength $names]
  1421.     
  1422.     set type [string tolower [lindex $vals 0]]
  1423.     set citekey [lindex $vals 1]
  1424. #     message "$citekey"
  1425.     # Don't process @string entries
  1426.     if {$type == "string"} {
  1427.         set lines [getText $top $bottom]
  1428.         return $lines
  1429.     }
  1430.     # Find length of longest field name
  1431.     set nmlen 0
  1432.     foreach nm $names {
  1433.         set len [string length $nm]
  1434.         if {$len > $nmlen} { set nmlen $len }
  1435.         if {![info exists useBrace($nm)]} { set useBrace($nm) 0 }
  1436.     }
  1437.     
  1438.     # Format first line
  1439.     set lines "@${type}${bibOpenEntry}${citekey},\r"
  1440.     
  1441.     # Format each field on a separate line
  1442.     for {set ifld 2} {$ifld < $nfld} {incr ifld} { 
  1443.         set nm [lindex $names $ifld]
  1444.         set vl [lindex $vals $ifld]
  1445.         if {$vl != "" || ! $BibmodeVars(zapEmptyFields) || 
  1446.                 [lsearch $rqdFld($type) $nm] >= 0} {
  1447.             set pad [expr $nmlen - [string length $nm]]
  1448.             
  1449.             if {$BibmodeVars(alignEquals)} {
  1450.                 set pref "${bibIndent}$nm[string range $spc 1 $pad] ="
  1451.             } else {
  1452.                 set pref "${bibIndent}$nm =[string range $spc 1 $pad]"
  1453.             }
  1454.             set ind [string range $spc 1 [string length $pref]]
  1455.             
  1456.             # Delimit field, if appropriate
  1457.             set noBrace [expr ($useBrace($nm) == 0 && [isNum $vl]) || [hasCat $vl]]
  1458.             if {$noBrace == 0 && [string first " " $vl] < 0} {
  1459.                 set noBrace [expr [lsearch $bibAbbrevs [string tolower $vl]] >= 0]
  1460.             }
  1461.             if {$noBrace != 0} {
  1462.                 set vl "$vl,"
  1463.             } else {
  1464.                 set vl "${bibOpenQuote}${vl}${bibCloseQuote},"
  1465.             }
  1466.             
  1467.             set pieces [split $vl "\r"]
  1468.             append lines "$pref [lindex $pieces 0]\r"
  1469.             foreach piece [lrange $pieces 1 end] {
  1470.                 append lines "$ind  $piece\r"
  1471.             }
  1472.         }
  1473.     }
  1474.     append lines "$bibCloseEntry\r"
  1475.     return $lines
  1476. }
  1477.  
  1478. ###########################################################################
  1479. # Get the name of the field that starts before the given position,  
  1480. # $pos.  The positions $top and $bottom restrict the range of the 
  1481. # search for the beginning and end of the field; typically, $top and
  1482. # $bottom will be the limits of a given entry.
  1483. #
  1484. proc getFldName {pos top} {
  1485.     set fldPat {[,     ]+([^     =,\{\}\"\']+)[     ]*=[     ]*}
  1486.     if {![catch {search -f 0 -r 1 -m 0 -i 1 -s -limit $top "$fldPat" $pos} mtch]} {
  1487.         set theText [eval getText $mtch]
  1488.         regexp -nocase $fldPat $theText allofit fldnam
  1489.         return $fldnam
  1490.     } else {
  1491.         return {citekey}
  1492.     }
  1493. }
  1494.  
  1495. ###########################################################################
  1496. #  Set the quote characters for quoted fields based on the value of the 
  1497. #  flag $bibUseBrace
  1498. proc bibFieldDelims {} {
  1499.     global BibmodeVars bibOpenQuote bibCloseQuote
  1500.     if {$BibmodeVars(fieldBraces)} {
  1501.         set bibOpenQuote "{"
  1502.         set bibCloseQuote "}" 
  1503.     } else {
  1504.         set bibOpenQuote {"} 
  1505.         set bibCloseQuote {"} 
  1506.     }
  1507. }
  1508.  
  1509. proc bibEntryDelims {} {
  1510.     global BibmodeVars bibOpenEntry bibCloseEntry
  1511.     if {$BibmodeVars(entryBraces)} {
  1512.         set bibOpenEntry "{"
  1513.         set bibCloseEntry "}" 
  1514.     } else {
  1515.         set bibOpenEntry "("
  1516.         set bibCloseEntry ")"
  1517.     }
  1518. }
  1519.  
  1520. proc isBibFile {} {
  1521.     set fileName [win::Current]   
  1522.     set ext [file extension $fileName]
  1523.     return [string match ".bib" [string tolower $ext]] 
  1524. }
  1525.  
  1526. proc hasNumVal {str} {
  1527.     expr ! [catch {expr $str}]
  1528. }
  1529. proc isNum {str} {
  1530.     regexp {^[     ]*[0-9]+[     ]*$} $str mtch
  1531. }
  1532. proc hasCat {str} {
  1533.     regexp {\#} $str mtch
  1534. }
  1535.  
  1536. ###########################################################################
  1537. # Take a list of lists that point to selected entries and copy these into
  1538. # a new window.  The beginning and ending positions for each entry must 
  1539. # be the last two items in each sublist.  The rest of the sublists are
  1540. # ignored.  It is assumed that each sublist has the same number of items.
  1541. #
  1542. proc writeEntries {entryPos nondestructive {beg {0}} {end {-1}}} {
  1543.         global BibmodeVars
  1544.         if {$end < 0} {set end [maxPos]}
  1545.         set llen [expr [llength [lindex $entryPos 0]] - 1]
  1546.         set llen1 [expr $llen-1]
  1547.         foreach entry $entryPos {
  1548.             set limits [lrange $entry $llen1 $llen]
  1549.             append lines [eval getText $limits]
  1550.         }
  1551.         set overwriteOK [expr $nondestructive || ! [isBibFile]]
  1552.         if {$BibmodeVars(overwriteBuffer) && $overwriteOK} {
  1553.             deleteText $beg $end
  1554.             insertText $lines
  1555.             goto $beg
  1556.         } else {
  1557.             set begLines [getText 0 [lineStart $beg]]
  1558.             set endLines [getText [nextLineStart $end] [maxPos]]
  1559.             new -n {*BibTeX Sort/Search*} -m Bib
  1560.             insertText $begLines
  1561.             insertText $lines
  1562.             insertText $endLines
  1563.             goto $beg
  1564.             setWinInfo dirty 0
  1565.             catch shrinkWindow
  1566.         }
  1567. }
  1568.  
  1569. ###########################################################################
  1570. # Set a named mark for each entry, using the cite-key name
  1571. #
  1572. proc Bib::MarkFile {} {
  1573.     global BibmodeVars
  1574.      global bibTopPat bibTopPat1 bibTopPat2
  1575.     set pos 0
  1576.     while {![catch {search -f 1 -r 1 -m 0 -i 0 -s $bibTopPat1 $pos} res]} {
  1577.         set start [lindex $res 0]
  1578.         set end [nextLineStart $start]
  1579.         set text [getText $start $end]
  1580.         set lab ""
  1581.         if {[regexp $bibTopPat2 $text mtch type citekey]} {
  1582.             if {[string tolower $type] != "string" || $BibmodeVars(markStrings)} { 
  1583.                 setNamedMark $citekey [lineStart [expr $start - 1]] $start $start
  1584.             }
  1585.         }
  1586.         set pos $end
  1587.     }
  1588. }
  1589.  
  1590. ###########################################################################
  1591. # Report the number of entries of each type
  1592. #
  1593. proc countEntries {} {
  1594.     global entryNames
  1595.      global bibTopPat bibTopPat1 bibTopPat2
  1596.     
  1597.     set pos 0
  1598.     set count 0
  1599.     catch {unset type}
  1600.     
  1601.     while {![catch {search -f 1 -r 1 -m 0 -i 0 -s $bibTopPat $pos} res]} {
  1602.         incr count
  1603.         set start [lindex $res 0]
  1604.         set end [nextLineStart $start]
  1605.         set text [getText $start $end]
  1606.         set lab ""
  1607.         if {[regexp $bibTopPat $text mtch entryType]} {
  1608.             set entryType [string tolower $entryType]
  1609.             if {[catch {incr type($entryType)} num]} {
  1610.                 set type($entryType) 1
  1611.             }
  1612.         }
  1613.         set pos $end
  1614.     }
  1615.     new -n {*BibTeX Statistics*} -m Bib
  1616.     foreach name [lsort [array names type]] {
  1617.         if {$type($name) > 0} {
  1618.             append lines [format "%4.0d  %s\n" $type($name) $name]
  1619.         }
  1620.     }
  1621.     append lines "----  -----------------\n"
  1622.     append lines [format "%4.0d  %s\n" $count "Total entries"]
  1623.     insertText $lines
  1624.     goto 0
  1625.     setWinInfo dirty 0
  1626.     catch {shrinkWindow 1}
  1627. }
  1628. #--------------------------------------------------------------------------
  1629. # command-double-clicking:
  1630. #--------------------------------------------------------------------------
  1631.  
  1632. ###########################################################################
  1633. # In Bib mode, Cmd-double-clicks resolve abbrevs and cross-refs
  1634. #
  1635. proc Bib::DblClick {from to} {
  1636.     global bibTopPat bibTopPat1 bibTopPat2
  1637.     
  1638.     set limits [getEntry $from]
  1639.     set top [lindex $limits 0]
  1640.     set bottom [lindex $limits 1]
  1641.  
  1642.     # Extend selection to largest string that could be an entry reference
  1643.     set text [string trim [eval getText [BibExtendClick $from $to $top $bottom]]]
  1644.     
  1645.     # Get the citekey of current entry, so we can avoid jumping to it    
  1646.     set citekey {}
  1647.     regexp $bibTopPat2 [getText $top $bottom] mtch type citekey ]
  1648.     set fldName [getFldName $from $top]
  1649.  
  1650.     if {[string length $text] == 0 || $text == $citekey || $fldName == $text || 
  1651.         ($fldName == "citekey" && [string tolower $type] != "string")} {
  1652.         message "Command-double-click on abbreviations and crossref arguments"
  1653.         return
  1654.     }
  1655.  
  1656.     # Jump to the mark for the specified citation, if a mark exists...
  1657.     # ...otherwise, do an ordinary search for the cite key
  1658.     pushPosition    
  1659.     set searchPat "$bibTopPat\[     \]*[quote::Regfind $text]\[     ,\}\)\]"
  1660.     if {![catch {search -f 1 -r 1 -i 1 -m 0 $searchPat 0} mtch]} {
  1661.         goto [lindex $mtch 0]
  1662.     } else {
  1663.         popPosition
  1664.         select $from $to
  1665.         if {$fldName == "crossref"} {
  1666.             message "Cross-reference \"$text\" not found"
  1667.         } else {
  1668.             message "Command-double-click on abbreviations and crossref arguments"
  1669.         }
  1670.         return
  1671.     }
  1672.     message "Use Ctl-. to return to original position"
  1673.     return
  1674. }
  1675.  
  1676. # Extend the selection around the initial selection {$from,$to}
  1677. # Extension is restricted to the range {$top,$bottom} (the current entry)
  1678. proc BibExtendClick {from to top bottom} {
  1679.     if {$to == 0} { set to $from }
  1680.     set result [list $from $to]
  1681.     if {![catch {search -f 0 -r 1 -s -m 0 -l $top "\[,\{\]\"\'=" $from} mtch0]} {
  1682.         if {![catch {search -f 1 -r 1 -s -m 0 -l $bottom "\[,\}\]\"\'=" $to} mtch1]} {
  1683.             set from [lindex $mtch0 1]
  1684.             set to [lindex $mtch1 0]
  1685.             # Check for illegal chars embedded in the selection
  1686.             if {[regexp "\[\{\}\]=" [getText $from $to]] == 0} {
  1687.                 set result [list $from $to]
  1688.             }
  1689.         }
  1690.     }
  1691.     return $result
  1692. }
  1693.  
  1694. #===============================================================================
  1695. proc pcite {} {
  1696.     set words [getline "Citation keys" ""]
  1697.     if {![llength $words]} {error "No keys"}
  1698.     
  1699.     set pattern {@}
  1700.     foreach w $words {
  1701.         append pattern "(\[^@\]+$w)"
  1702.     }
  1703.     
  1704.     foreach entry [findEntries $pattern] {
  1705.         set res [getFields [car $entry]]
  1706.         set title [lindex [cadr $res] [lsearch [car $res] "title"]]
  1707.         set citekey [lindex [cadr $res] [lsearch [car $res] "citekey"]]
  1708.         set matches($title) $citekey
  1709.         set where($title) [car $entry]
  1710.     }
  1711.     if {![info exists matches]} {alertnote "No citations"; return}
  1712.     set title [listpick -p "Citation?" [lsort [array names matches]]]
  1713.     putScrap $matches($title)
  1714.     alertnote $matches($title)
  1715.     goto $where($title)
  1716. }
  1717.